home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The X-Philes (2nd Revision)
/
The X-Philes Number 1 (1995).iso
/
xphiles
/
hp48_2
/
mpe_v1_0.doc
< prev
next >
Wrap
Internet Message Format
|
1995-03-23
|
43KB
From comp.sys.handhelds Fri May 24 22:30:22 1991
Path: seq!ecsgate!mcnc!taco!lll-winken!elroy.jpl.nasa.gov!swrinde!zaphod.mps.ohio-state.edu!wuarchive!udel!haven.umd.edu!decuac!pa.dec.com!nntpd.lkg.dec.com!ryn.mro4.dec.com!pinbot.enet.dec.com!ervin
From: ervin@pinbot.enet.dec.com (Joseph James Ervin)
Newsgroups: comp.sys.handhelds
Subject: HP48 MPE V1.0 User's Guide
Message-ID: <4842@ryn.mro4.dec.com>
Date: 15 May 91 19:15:28 GMT
Sender: guest@ryn.mro4.dec.com
Reply-To: ervin@pinbot.enet.dec.com (Joseph James Ervin)
Organization: Digital Equipment Corporation
Lines: 894
MPE
A Multi-Programming Environment for the HP-48SX
by
Joe Ervin
1 PURPOSE
The purpose of this document is to describe the function and use of
the Multi-Programming Environment, written by Joe Ervin.
2 INTRODUCTION
The Multi-Programming Environment (MPE) is a set of machine language
routines, data structures, and Star macros which implement an
environment whereby the software developer can easily program
concurrent tasks. The immediate use of MPE is for graphics animation
such as in games programming, where multiple objects need to be
animated on the screen at the same time (bullets, explosions, pac-men,
etc.), however an environment such as this can be extremely useful for
periodic keyboard polling and other events that need to occur from
time to time.
This distribution of MPE includes the STAR source code for MPE version
1.0, as well as a set of process definitions which comprise a simple
interactive graphics demo, showing the main features of MPE. The
source code and the uuencoded file have been posted separately. If
you do not have access to either of these documents, please send mail
to "ervin@pinbot.enet.dec.com" and I will mail them to you.
3 USER'S GUIDE
The remainder of this document comprises the MPE User's Guide. This
guide discusses the basic structure of a process under MPE, and then
goes on to describe the various routines and macros and how they are
used. These routines and macros allow the user to do such things as
process scheduling, process entry-point redirection, and interprocess
communication. Finally, the example set of processes which comprise
the demo are described at the end of the user's guide.
Page 2
3.1 Process Structure
The basic idea behind MPE is that the programmer defines one or more
"processes" which are activated, or invoked by MPE. MPE keeps a list
of all processes which are waiting to run, and runs the processes as
they come due. As each process is run, it is deleted from the
run-list, thus requiring processes to be rescheduled after each
invocation if repeated execution is desired.
The manner in which MPE handles processes is as follows. The run-list
contains a set of process ID/time pairs, where the "ID" is an 8-bit
process ID, and the "time" is the absolute time (in clock ticks) at
which that process should be run. The clock ticks referred to here
are the same "ticks" as those that are associated with the TICKS
command described in the HP48 owners manual. There are 8192 clock
ticks per second. The RUN_LIST is a data structure used and
maintained by MPE. The casual programmer need not be concerned with
its workings.
The association between processes and IDs is implied through the use
of global labels in the assembler code for the processes. MPE expects
that each process will have at its beginning a label of the form:
PROCESSx_INIT:
...where the x in PROCESSx_INIT is a decimal number from 1 to 255. A
value of 0 may NOT be used as a process ID since the MPE reserves this
ID for the scheduler.
The portion of MPE that manages the run-list is called the
"scheduler", and will be referred to as such for the remainder of this
document. When an application written using MPE is first started, the
scheduler's run-list is empty. In order to activate the application,
MPE automatically schedules process #1 for immediate execution. As a
result, the control flow will pass immediately to the code located at
PROCESS1_INIT:.
NOTE
This is the only time that a process will be invoked
automatically by the MPE. It is the responsibility of
the programmer to ensure that all processes which
comprise the application are scheduled by the
application itself. Processes are scheduled by using
the ADD_PROCESS routine, described below.
Initially, MPE defines the entry points of all processes to be the
PROCESSx_INIT label of each process. In many applications, however,
it will be necessary for a process to execute its initialization code
only once; the first time the process is activated. In order to
facilitate the distinction between process initialization code and the
main process body, MPE includes a macro called PROCESS_START, which
allows the process to redefine its entry point.
Page 3
The PROCESS_START macro takes two parameters; the ID of the process
whose entry point is to be modified, and the label corresponding to
the desired entry point. For example, Figure 1 shows process #1 using
the PROCESS_START macro to alter its entry point. This example also
shows the use of several of the other facilities in MPE. These other
facilities will be described in later sections.
;**********************************************************************
PROCESS1_INIT: ; The initial entry point for process #1.
PROCESS_START 1, PROCESS1_CODE
; Changes the entry point of process #1 to the
; PROCESS1_CODE label for subsequent
; invocations.
.
(Initialization code for process #1.)
.
PROCESS1_CODE: ; The main body of process #1 starts here.
.
(Body of process #1)
.
SAVE_CONTEXT
. ; This causes MPE to save a "snapshot" of this
. ; process's register context (B, D, D0, D1, R0-R4)
. ; so that the next time this process is started, its
. ; registers will contain the same data as at the
. ; time SAVE_CONTEXT is called. A and C are not saved.
.
RESCHEDULE: ; Now we want to schedule this process to execute again
; in 1 second.
ADDR CUR_TIME, D0
; ADDR is a standard HP48 macro found in the HP48.STAR
; macro library by Jan Brittenson. CUR_TIME is a global
; MPE variable which holds a copy of the time
; (in ticks) when this process was activated.
MOVE.W @D0, C
CLR.W A
MOVE.P5 ^x1FFF, A
ADD.W A, C ;
; CUR_TIME + ^x1FFF ticks = CUR_TIME + 1 sec.
MOVE.P5 ^x1, A ; We are rescheduling process #1. This would
; be ^x2 for process #2, ^x3 for process #3,
; etc..
CALL ADD_PROCESS ; Schedule process #1 for execution
; at 1 sec. from when the current
; invocation started.
JUMP TO_SCHEDULER ; Jump back to the scheduler.
;**********************************************************************
Figure 1
Page 4
In addition to the PROCESS_START macro, there is the CUR_PROCESS_START
macro, which is very similar to PROCESS_START, but with small
differences. The CUR_PROCESS_START macro works only on the current
process. The CUR_PROCESS_START macro expects that the user has
previously loaded the address corresponding to the new entry point for
the current process into C.A. Invoking the CUR_PROCESS_START macro
will then take that address in C.A and cause the current process to
use that as its new entry point. The ADDR macro in the HP48 macro
library by Jan Brittenson is useful for loading C.A with the address
of the new entry point.
The main impetus for the CUR_PROCESS_START macro is to avoid the need
of multiple IF_PROC/ENDIF_PROC constructs for code which may be shared
by many processes. In games that require many objects on the screen,
each of which with its own process, it is conceivable that the number
of IF_PROC/ENDIF_PROC constructs required by the PROCESS_START macro
would be unwieldy.
3.2 Process Context
3.2.1 The SAVE_CONTEXT Routine
One of the main functions of MPE is to ensure that each process has
its own processor context. To do this, MPE provides the SAVE_CONTEXT
routine which automatically saves the processor registers of the
current process in a special data structure maintained by MPE for each
process. The registers saved are B, D, D0, D1, and R0-R4. Note that
MPE makes no attempt to save Registers A and C between invocations.
By calling the SAVE_CONTEXT routine, a process indicates to MPE that
the current contents of its registers should be used as the starting
contents when the process is next invoked. It is the responsibility
of the programmer to call SAVE_CONTEXT at an appropriate point in the
process when all the registers have the desired contents. MPE does
not provide a mechanism for saving or restoring individual registers.
The SAVE_CONTEXT routine alters A and C, but leaves all other
processor registers intact.
MPE also provides the SAVE_CONTEXT_BY_A routine which allows the user
to specify the register context for any arbitrary process. The user
provides a process ID in register A.B, and SAVE_CONTEXT_BY_A will
write the current register context into the context storage area
maintained by MPE for that process. Note that no error checking is
done on the ID provided in A.B, so the user should be careful with
using this routine.
3.2.2 The RESTORE_CONTEXT Routine.
Whenever a process comes due and is about to be executed, MPE uses the
RESTORE_CONTEXT routine to restore the process's register context,
after which MPE jumps to the process's entry point. The
Page 5
RESTORE_CONTEXT routine, together with the SAVE_CONTEXT routine can
also be useful to the programmer in general. The RESTORE_CONTEXT
routine restores the register context of the process whose ID is given
in A.B. Obviously, this routine effects all the processor registers.
The RESTORE_CONTEXT routine can be useful to the programmer whenever
information needs to be shared between processes. For example, in
some applications, it may be desirable to extract information that
another process is keeping in one of its processor registers. This
may be accomplished by using the RESTORE_CONTEXT routine, specifying
the other process ID in A.B. Note that it is probably advisable for a
process to save its own context via SAVE_CONTEXT before calling
RESTORE_CONTEXT to see the other process's registers. That way, after
the extracted data has been used, the process can restore its own
context if necessary and continue execution.
Note that although the SAVE_CONTEXT routine does not require any
arguments, the RESTORE_CONTEXT routine requires the process ID in A.B.
3.2.3 The CURRENT_CONTEXT_ID Variable
In order to keep track of where to save register context when the
SAVE_CONTEXT routine is called, MPE maintains a variable called
CURRENT_CONTEXT_ID. This is a one byte variable which contains a copy
of the current process's ID. While this variable is primarily for use
by the SAVE_CONTEXT routine, it comes in very handy for certain
applications.
One useful action that involves the CURRENT_CONTEXT_ID is that of
modifying the saved state of another process. This can be done by
first saving the current process's context, then restoring the other
process's context. At this point, the current process has access to
all the register context of the other process. By modifying these
contents, changing the CURRENT_CONTEXT_ID variable to the other
process's ID, and then executing the SAVE_CONTEXT routine, the other
process's context can be overwritten. The programmer must be very
careful to ensure that the CURRENT_CONTEXT_ID variable is returned to
the current process's ID before control is passed back to the
scheduler.
Another useful technique involving the CURRENT_CONTEXT_ID variable is
that of sharing code among several processes. This is discussed in
detail below.
To make accessing the CURRENT_CONTEXT_ID variable easier, MPE includes
a routine called GET_CURRENT_ID, which when called returns the current
process ID into A.B. This routine modifies the contents of A.
Page 6
3.3 Scheduling Processes - The ADD_PROCESS Routine
As stated above, it is necessary for the user's code to handle the
scheduling of all processes, except for the initial execution of
process #1 at startup. Processes are scheduled by use of the
ADD_PROCESS routine. This routine takes as its arguments a one byte
process ID in register A, and a 16-nibble absolute time (ticks) in
register C. ADD_PROCESS then schedules the specified process for
execution at the specified time.
The ADD_PROCESS routine modifies A, C, B, D, D0, D1, and R0, so it is
recommended that this routine be executed only during the
initialization phase of a process, after the process has saved its
register context via the SAVE_CONTEXT routine, or any other time when
the contents of these registers is not critical to the process.
3.4 The CUR_TIME Variable
In order to reschedule a process at a certain approximate interval it
is necessary that the process have knowledge of the system time at the
point when the process was invoked. MPE provides this information in
the CUR_TIME variable. The CUR_TIME variable holds a 16 nibble value
representing the system time when the current process was invoked. To
schedule itself on regular intervals, a process should read the time
value at CUR_TIME, add in a time delay, and reschedule itself for
execution at the resulting future time. Figure 2 shows a typical use
of CUR_TIME. In this example, process #3 is rescheduling itself for
CUR_TIME plus 1 second. This causes process #3 to execute on 1 second
intervals.
;********************************************************************
; Reschedule process #3 for execution in 1 second.
ADDR CUR_TIME, D0 ; Point D0 to the CUR_TIME
; variable.
MOVE.W @D0, C ; C.W now contains the time
; when this process was
; invoked by the scheduler.
MOVE.P5 ^x1FFF, A ; ^x1FFF = ^d8192 = 1 second.
ADD.W A, C
MOVE.P2 ^d3
CALL ADD_PROCESS ; Add the process to the RUN_LIST.
JUMP TO_SCHEDULER ; Return control to the scheduler.
;********************************************************************
Figure 2
The CUR_TIME variable can be accessed by using the ADDR macro from the
HP48 macro library by Jan Brittenson. This method also works well for
accessing any other MPE variables. The CUR_TIME variable can also be
Page 7
accessed by calling the GET_CUR_TIME routine, which returns the
CUR_TIME variable in C.W. This routine modifies register A.
3.5 Returning Control To The Scheduler
After a process has completed its useful work for the current
invocation, it needs to save its register context via the SAVE_CONTEXT
routine, and then reschedule itself if desired. Finally, the last
thing a process does is return control to the scheduler by jumping to
the MPE label TO_SCHEDULER as follows:
JUMP TO_SCHEDULER ; Returns control to the scheduler.
The user should be careful to use the JUMP instruction here and NOT
the CALL instruction.
3.6 Sharing Process Code
In many applications, several processes may be needed which do
essentially the same thing. Consider the situation where an
application wants several processes which do very similar things, such
as "bullets" flying about on the screen as is common in many popular
arcade games. Since each of these processes would be running
essentially identical code, it would be very useful if each of the
"bullet" processes could actually share the same code. This can
easily be accomplished by placing multiple PROCESSxINIT: labels at
the top of the process.
One problem that soon becomes apparent, however, is that this shared
code must inevitably perform some actions that are specific to each
process. It is therefore necessary that the shared code has a way of
determining which process is currently executing. One example of this
is that since these processes will need to reschedule themselves, the
shared code must have some way of rescheduling the correct process.
By accessing the CURRENT_CONTEXT_ID, the shared code can determine the
ID of the process it is currently servicing.
The CURRENT_CONTEXT_ID variable can be accessed by using the ADDR
macro from the HP48 macro library by Jan Brittenson, as is shown
below. This method also works well for accessing any other MPE
variables.
;********************************************************************
; Check which process is currently running.
ADDR CURRENT_CONTEXT_ID, D0 ; Point D0 to the
; CURRENT_CONTEXT_ID variable.
MOVE.B @D0, C ; C.B now contains the ID of the
; current process.
Page 8
;********************************************************************
Figure 3
Because of the usefulness of code-sharing, MPE contains a macro which
makes the conditional execution of code based on the current process
ID very simple.
The IF_PROC and ENDIF_PROC macros allow the programmer to easily cause
the execution of a specific section of code conditioned on the current
context ID. The format of this structure is as follows:
;********************************************************************
IF_PROC n ; where "n" is the process ID in question.
.
.
(ML code to be executed only if process #n is current.)
.
.
ENDIF_PROC
;********************************************************************
Figure 4
NOTE
The user should be aware that the IF_PROC macro
modifies registers A and C regardless of which process
ID is current. For this reason, A and C will be
altered whenever IF_PROC is used. The user should
therefore make no attempt to pass values into an
IF_PROC/ENDIF_PROC construct using registers A or C.
As an example of when to use IF_PROC, consider a section of code which
is shared by processes #1, #2 and #3. Let us assume that the process
needs to reschedule itself after executing. It is therefore necessary
that the code pass the proper ID in register A to the ADD_PROCESS
routine, regardless of which process (#1, #2, or #3) is executing the
code. The following example represents one way that this could be
accomplished.
Page 9
; *****************************************************************
; Example of how to use IF_PROC.
; This code assumes that the next desired run-time for this process
; has been loaded into R0.
IF_PROC 3
MOVE.W R0, C ; Next time process should be run.
MOVE.P2 ^d3, A ; ID for this process into A.B.
JUMP PROCESS_SCHED
ENDIF_PROC
IF_PROC 4
MOVE.W R0, C ; Next time process should be run.
MOVE.P2 ^d4, A ; ID for this process into A.B.
JUMP PROCESS_SCHED
ENDIF_PROC
IF_PROC 5
MOVE.W R0, C ; Next time process should be run.
MOVE.P2 ^d5, A ; ID for this process into A.B.
JUMP PROCESS_SCHED
ENDIF_PROC
PROCESS_SCHED: CALL ADD_PROCESS
; Schedule the current process to
; run at the time given in C.
JUMP TO_SCHEDULER ; Return control to the scheduler.
; *****************************************************************
Figure 5
3.7 Interprocess Communication
In previous sections it was described how interprocess communication
could be done by modifying the latent process's context. While this
is valid, it is a terribly inefficient means for communicating between
processes. A much better way is to use global variables which are
shared between processes. Since processes are never interrupted by
the scheduler, there is no need to synchronize access of the shared
variables, so processes may pass information back and forth freely
using this scheme.
3.8 Process Timing
One issue that the programmer needs to be aware of when running under
MPE is that of process latency, particularly under heavy CPU loading,
and the effect that this latency has on process timing.
Consider a scenario where many processes are running under MPE.
Page 10
Looking at two of these processes, say #1 and #2, let us pretend that
process #1 always schedules itself to run at CUR_TIME + 1/32th
seconds, while process #2 always schedules itself to run at CUR_TIME +
1/16 seconds. One would therefore expect that process #1 would
execute 32 times each second, and that process #2 would execute 16
times each second. If the CPU is lightly loaded, then this is
(almost) true. If, however, the CPU is heavily loaded by the other
processes running under MPE, then processes #1 and #2 will undoubtedly
run less frequently than their reschedule intervals would indicate.
Furthermore, both processes will execute the same number of times in
any given time interval. The exact frequency of repetition will be
determined by the degree of CPU loading and the number of processes
running under MPE.
The reason for this is as follows. When the CPU is heavily loaded,
processes which schedule themselves a very short time into the future
will always be overdue when the scheduler checks them. In our
scenario, this means that processes #1 and #2 will always be overdue
by the time the scheduler gets a chance to check them in the run-list.
The result is that each process will be run exactly once for each pass
the scheduler makes through the run-list.
Another way to understand the problem is to consider a simple graphics
process whose job is to move a "bullet" across the screen at a given
rate. One way to do this would be to have the process move the bullet
a pixel or so and then reschedule itself for a short time into the
future. The time for which the process reschedules itself could be
given as the time the process was most recently executed (as stored in
CUR_TIME) plus some incremental time chosen to give a desired
repetition rate for the process. While this simple approach will work
well when the CPU is lightly loaded, it does not work as well when the
CPU is very heavily loaded.
What happens when the CPU is heavily loaded is that processes may sit
in the scheduler's run-list long past the time for which they were
scheduled, simply because the CPU is very busy running the other
processes in the system. Considering the "bullet" process, this
results in "lost" time, and causes the bullet on the screen to slow
down. The reason time is "lost" from the perspective of the bullet is
that being ignorant of absolute time (the time indicated by the system
clock) the process always reschedules itself relative to when its
current invocation started, as indicated by CUR_TIME. The process
makes no attempt to "catch-up" to where the bullet would have been if
the CPU had been lightly loaded.
One solution to this problem is to schedule processes based not on the
time value stored in the CUR_TIME variable, but rather based on
current time value in the system clock. Doing this enables a process
to recover time that was lost to other processes. In the case of our
"bullet" process, the process code could examine the system clock,
determine how much real time has passed since the process was last
invoked, and then move the bullet a number of pixels consistent with
the desired rate of travel. The visual result on the screen in this
case is that the bullet has the desired average rate of travel across
the screen, regardless of CPU loading. As a side effect, the motion
Page 11
of the bullet may appear a little "jittery" since the process may move
it several pixels in rapid succession during each invocation, rather
than moving it one pixel per invocation as in the simpler
implementation.
The programmer should be very careful, however, when programming
processes which attempt to "catch up" to the system clock in this
manner. To see how this type of process definition can lead to
problems, consider a heavily loaded system with two bullet processes
which run based on the absolute system time as described above.
To facilitate the reading of the system clock, MPE includes a routine
called GET_TICKS which returns the current (16 nibble) value of the
system clock to register C. This routine modifies register A, but
leaves all other processor registers intact.
Examples of both implementations of the "bullet" process can be seen
in the demo processes included with the MPE distribution. This demo
also illustrates the problems regarding the relative frequencies of
process invocation in a heavily loaded system.
3.9 Assembly-time Errors
Because of the way MPE sets up its data structures for managing the
processes, it is necessary that the processes be defined by the
programmer with contiguous process IDs. For example, if the
programmer has defined process #4, then processes #1-#3 must also have
been defined. If the programmer leaves any "holes" in the process
numbering scheme, MPE will generate an error message to that effect at
assembly-time.
3.10 Run-time Errors
There are two common errors that the programmer might be likely to
make when using MPE. Both involve the use of the ADD_PROCESS routine.
The first scenario is when the user attempts to schedule more
processes than there are process slots in the scheduler's run-list.
The current implementation of STAR allows only 10 pending processes in
the run-list at a time. If the user attempts to schedule more than 10
processes concurrently, MPE will push onto the stack an error code of
99d, indicating that the run-list overflowed, and the ID of the
process which made the call to ADD_PROCESS when the failure occurred.
Both of these values are pushed as short binary integers. MPE then
halts execution of the machine language code and continues with the
RPL thread.
The size of the run_list can be easily increased to accommodate
applications which need more than 10 processes scheduled concurrently.
Refer to the section below on "MPE Customization".
The other potential misuse of ADD_PROCESS is when the user calls
Page 12
ADD_PROCESS with an invalid process ID. I did this once while
debugging MPE and my machine promptly crashed with "memory lost".
Very unforgiving.
Therefore, MPE now checks the ID passed into ADD_PROCESS to make sure
it corresponds to one of the processes defined by the user. If not,
then MPE pushes an error code of 98d onto the stack as a short binary,
indicating that an invalid ID was passed into ADD_PROCESS. MPE then
also pushes as short binaries the ID of the process which called
ADD_PROCESS, and the erroneous ID, in that order. MPE then halts
execution of the machine language code and continues with the RPL
thread.
3.11 MPE Customization
The current implementation of MPE will allow up to 20 defined
processes. The user should note, however, that the version of MPE in
this distribution will only allow as many as 10 processes to be
concurrently scheduled. This has been done to optimize performance
for the set of process definitions which comprise the MPE demo
application. This can easily be changed by the programmer to hold as
many processes as you need. Read on.
The scheduler scans through the run list in a circular fashion,
updating its copy of the system time once per pass through the
run-list. As the scheduler does a pass through the run-list, it
checks the time stamp of any valid processes against its copy of the
system time and runs the associated process if its time stamp is
earlier than the current system time. Because of this circular scan
algorithm, it is highly advisable to limit the size of the run-list to
the number of processes which the programmer expects to have
concurrently scheduled. In this way, the scheduler does not waste
valuable CPU time checking run-list slots which will never be filled.
The run-list is a static data structure located at the STAR label
"RUN_LIST:". The programmer should increase or decrease the size of
this data allocation as needed, being sure to update the RUN_LIST_SIZE
variable at the same time. (RUN_LIST_SIZE is a STAR variable which is
defined just above where the RUN_LIST is located in the MPE sources.)
For each new slot in the RUN_LIST, the programmer should add a "DATA.B
0" to hold the process ID and then a "DATA.W 0" to hold the process
time stamp. The RUN_LIST_SIZE variable indicates the number of slots
in RUN_LIST.
If the current maximum of 20 processes is insufficient for a given
application, then the programmer should add lines of code to the MPE
data structure and initialization sections which appears just before
and after the START: label. The lines which will need to be added
should be fairly obvious, and will have the form of:
Page 13
(appearing just before the START: label)
GEN_HEADERS 21
GEN_HEADERS 22
.
.
.
GEN_DESCRIPTOR 21
GEN_DESCRIPTOR 22
.
.
.
(...and then appearing just after the START: label)
HEADER_INIT 21
HEADER_INIT 22
.
.
.
Figure 6
The new lines must be added to the end of each section (after the line
corresponding to process #20). You will need to make similar
additions in the FILL_DESCRIPTORS macro, adding more "FILL xx" lines
for the additional processes. The need to make the above additions
will be removed in a later version of MPE.
If anyone needs more than 20 processes, please send mail. I want to
see your application :-)
3.12 Assembler Considerations
Due to limitations in the current version of Star (V1.04.4),
applications running under MPE should be assembled with the
jumpification feature turned off. This is done by using the -j switch
on the command line when STAR is run. For help on the various
switches which can be used on the STAR command line, invoke STAR with
the -h switch, which will cause STAR to print out its standard help
text.
Page 14
4 MPE DEMO PROCESSES
Included with this distribution of MPE is a set of seven process
definitions which demonstrate the basic features of MPE. The
following sections will describe briefly what each of these processes
do and how they interact.
4.1 Application Overview
This demo application is a simple demonstration of how MPE can be used
to do graphical animation of several objects on the screen at once.
This also provides a good visual demonstration of the CPU loading
effects described in earlier sections.
Basically, the application consists of a fire button and four
"bullets". This application uses the [+] button as the fire button.
When the user starts the application, the screen blanks and waits for
the user to press the fire button. For each press of the [+] button,
the application will fire a bullet. The bullets start at the upper
left of the screen and move down the screen, row by row, until they
reach the bottom of the screen. When any bullet reaches the lower
right of the screen, the application stops. When the user has fired
all four bullets, the fire button is disabled for the remainder of the
application. The [.] key quits the application. Refer to the
following section for how to download and run the application.
4.2 Downloading And Running The Application
Included in this distribution is the uuencoded binary file. This
binary file is a directory containing the machine language code which
makes up the MPE application, and a short RPL program which runs it.
The user should uudecode this file and then download the resulting
binary file in the usual manner to the HP48SX (make sure to set the
HP48SX kermit to binary mode. The RPL program, "DEMO", in the new
directory serves only to clear the display and set the display to PICT
memory before running the machine language application ("XXX"). You
should _not_ run the "XXX" program directly, since if PICT memory has
been purged (by doing a PICT PURGE), then the application may corrupt
memory. Once started by pressing the button labeled "DEMO", the
machine language program can be aborted at any time by pressing the
[.] button.
4.3 Process Descriptions
The following sections describe each of the seven processes which make
up the application. Note that processes #1 and #2 are not integral to
the application and could be omitted.
Page 15
4.3.1 Process #1
This is the process which is auto-executed by MPE at startup. In
terms of the application, the only thing that this process does is to
start up the keyboard scanner process (#7). Another function that
this process performs, in conjunction with process #2 is to do a
register context integrity test periodically while the application is
running. This was written mainly for debugging MPE, but may be useful
to the programmer in general, so I have left it in. It runs once per
second, so it does not add significant overhead to the application.
4.3.2 Process #2
This process does nothing except clear all the processor registers.
The purpose of this process is to work with process #1 to implement a
register context integrity test.
4.3.3 Processes #3, #4, And #5.
These processes are grouped together here because they share the same
process definition. This process definition shows a good example of
how to share code between two or more processes. The code for this
process definition makes use of the IF_PROC/ENDIF_PROC macros to do
conditional execution of certain sections of code based on the current
context ID. For example, the three bullets owned by processes #3, #4,
and #5 all move at different speeds on the screen. This was done here
to visually show the independence of the bullet processes running in
the system. The process definition uses the IF_PROC/ENDIF_PROC macros
to schedule the correct process at the correct interval, thus
determining the speed of each bullet.
One thing to note is that this process definition does not attempt to
control the speed of a bullet with great accuracy. In other words, as
the four bullets are fired and the CPU becomes more heavily loaded,
the speed of the bullets corresponding to processes #3, #4, and #5
will slow down slightly. This is due to process latency in the
scheduler. Process #6 shows a similar bullet process, but one which
keeps track of absolute time and maintains an accurate velocity on the
screen, independent of CPU loading.
4.3.4 Process #6
This process definition is very similar to that which defines
processes #3, #4,and #5. The main difference is that process #6 keeps
track of absolute time, making sure that its bullet moves at exactly
the velocity indicated in the process definition. In this way, even
though the bullet for this process does not start until the CPU is
already very busy with the other three bullets, it will maintain its
velocity at the expense of the other three bullets.
Page 16
The effects of the use of absolute time in this process, versus
relative time in the other three bullet processes, can be seen in two
ways. The first way is that after firing the four bullets, it should
be discernible that the fourth bullet (process #6) moves slightly
faster than the second one (process #4). By examining the definitions
of these processes, one quickly sees that processes #4 and #6 have the
same rescheduling interval. The difference in speed is therefore
attributable to the time that process #4 loses while sitting in the
RUN_LIST waiting to be run by the scheduler. Since process #6 keeps
track of absolute time internal to itself, this queuing time is
accurately recovered each time the process is invoked.
The second way that the use of absolute time in process #6 can be seen
is by pressing and briefly holding the [ON] key. Pressing this key
temporarily halts execution of the calculator, causing all processes
to pause. When the key is released, however, the bullet of process #6
will quickly "zip" ahead to catch up to where it should be. A similar
effect can also be seen by pressing and holding some other key
(besides [ON]). Since the keyboard controller causes lots of
interrupts in when this is done, it has the effect of heavily loading
the CPU. What can be seen on the display is that while the user holds
down a key and thereby loads the CPU, the three bullets which do not
use absolute timing slow down noticably, while the bullet for process
#6 maintains its correct average velocity. The manner in which
process #6 "catches up" to where it should be can be seen in the
bullet's jumpiness.
4.3.5 Process #7
This process comprises the keyboard scanner. This process simply
checks the keybuffer for the [+] key, and if it finds it then the next
bullet process is scheduled for execution. If the [.] key is pressed,
this process will cause the application to exit and continue with the
RPL thread. This process runs approximately 8 times per second.